<?php

/**
 * server
 */
class API_Server {

    /**
     * return code
     */
    const CODE_SUCCESS = 0;
    const CODE_SYSTEM_ERROR = 1;
    const CODE_PARAM_ERROR = 2;
    
    /**
     * option
     * @var array 
     */
    private $_option = array();
    
    /**
     * request 实例
     * @var Web_Request
     */
    protected $_request;

    /**
     * response 实例
     * @var Web_Response
     */
    protected $_response;
    
    /**
     * 构造函数
     * @param type $option
     */
    public function __construct($option = array()) {

        $this->_option = $option;
        
        $this->_request = Web_Request::getInstance();
        $this->_response = Web_Response::getInstance();
    }

    /**
     * handle
     */
    public function handle() {
        
        // log_id 关联
        $logId = $this->_request->post('log_id');
        if ($logId && !defined('LOG_ID')) {
            define('LOG_ID', $logId);
        }

        $param = $this->_request->postx();
        Logger::setParam('param', $param);

        // common param
        $client = $this->_request->post('client');
        $method = $this->_request->post('method');
        $timestamp = $this->_request->post('ts');
        $sign = $this->_request->post('sign');

        // check client
        if (!$this->_getSecret($client)) {
            Logger::setParam('invalid_client', $client);
            return $this->_outputError(new Exception('invalid client: '.$client, self::CODE_PARAM_ERROR));
        }
        
        // check timestamp
        if (abs(V::timestamp() - $timestamp) > 600) {
            Logger::setParam('invalid_timestamp', $timestamp);
            return $this->_outputError(new Exception('timestamp not match: '.$sign, self::CODE_PARAM_ERROR));
        }
        
        // check sign
        if (!$sign || $sign != $this->_generateSign($param)) {
            Logger::setParam('invalid_sign', $sign);
            return $this->_outputError(new Exception('invalid sign: '.$sign, self::CODE_PARAM_ERROR));
        }
        
        // check method
        if (!$method || empty($this->_option['methods'][$method])) {
            Logger::setParam('invalid_method', $method);
            return $this->_outputError(new Exception('invalid method: '.$method, self::CODE_PARAM_ERROR));
        }
        
        // check method
        $conf = $this->_option['methods'][$method];
        if (!$conf || empty($conf['callable'])) {
            Logger::setParam('invalid_method', $method);
            return $this->_outputError(new Exception('method not exists: '.$method, self::CODE_PARAM_ERROR));
        }
        try {
            $param['args'] = !empty($param['args']) ? $param['args'] : [];
            $args = $this->_buildParams($conf['args'], $param['args']);
            Logger::setParam('conf_args', $conf['args']);
            Logger::setParam('param_args', $param['args']);
        } catch (Exception $ex) {
            Logger::setParam('param_error', $ex->getMessage());
            return $this->_outputError($ex);
        }
        
        // execute
        try {
            Logger::setParam('callable', $conf['callable']);
            Logger::setParam('args', $args);
            $result = call_user_func_array($conf['callable'], $args);
            Logger::setParam('result', $result);
            return $this->_outputResult($result);
        } catch (Restful_Exception $e) {
            Logger::setParam('execute_error', $e->getMessage());
            return $this->_outputError($e);
        } catch (Exception $e) {
            Logger::setParam('execute_error', $e->getMessage());
            return $this->_outputError(new Restful_Exception('Unknown error', self::CODE_SYSTEM_ERROR));
        }
    }

    /**
     * _buildParams
     * 根据配置对参数数据验证
     * @throw  Exception
     * @return array
     */
    private function _buildParams($configArgs, $args) {

        $rargs = array();
        if (!is_array($configArgs)) {
            return $rargs;
        }

        foreach ($configArgs as $name => $def) {
            if ($def['required'] && !isset($args[$name])) {
                throw new Exception("Parameter missing: $name", self::CODE_PARAM_ERROR);
            }

            if (empty($args[$name])) {
                $value = isset($def['default']) ? $def['default'] : $this->_defaultValue($def['type']);
            } else {
                $value = $args[$name];
                switch ($def['type']) {
                    case 'int':
                        $value = intval($value);
                        break;
                    case 'string':
                        $value = strval($value);
                        break;
                    case 'array':
                        $value = $this->_buildArrayParam($value, $def);
                        break;
                    default:
                }
            }
            $rargs[] = $value;
        }
        
        return $rargs;
    }

    /**
     * _defaultValue
     *
     * @param  mixed $type
     * @return void
     */
    private function _defaultValue($type) {
        static $defaults = array(
            'string' => '',
            'array' => array(),
            'int' => 0,
            'bool' => false
        );

        return isset($defaults[$type]) ? $defaults[$type] : null;
    }

    /**
     * 格式化数组
     * @param <type> $value
     * @param <type> $def
     * @return <type>
     */
    protected function _buildArrayParam($value, $def) {

        if (!empty($def['max_size'])) {
            $value = array_slice($value, 0, $def['max_size']);
        }

        if (!empty($def['element_type'])) {
            switch ($def['element_type']) {
                case 'int':
                    $value = array_map('intval', $value);
                    break;
                case 'string':
                    $value = array_map('strval', $value);
                    break;
            }
        }

        return $value;
    }

    /**
     * _outputError
     *
     * @param  mixed $e
     * @return void
     */
    protected function _outputError($e) {

        $result = array();
        $result['code']    = $e->getCode();
        $result['message'] = $e->getMessage();
        $result['result']  = '';

        return $this->_output($result);
    }

    /**
     * _outputResult
     *
     * @param  mixed $res
     * @return void
     */
    protected function _outputResult($res) {

        $result = array();
        $result['code']    = 0;
        $result['message'] = 'ok';
        $result['result']  = $res;

        return $this->_output($result);
    }

    /**
     * output
     *
     * @param  mixed $result
     * @return void
     */
    protected function _output($result) {

        $output = '';
        switch (strtoupper($this->_request->post('format'))) {
            case 'JSON':
            default:
                $output = json_encode($result);
        }

        $this->_response->setBody($output);
        $this->_response->output();
        // log
        V::log('restful server finished. cost: '. V::runtime().'ms', Logger::LEVEL_INFO);
        return true;
    }
    
    /**
     * 获取 secret 
     * @param type $client
     */
    private function _getSecret($client) {
        
        return isset($this->_option['clients'][$client]) ? $this->_option['clients'][$client] : '';
    }

	/**
	 * _generateSign
	 * sign 生成方法
	 *
	 * @param  mixed $params
	 * @return void
	 */
	private function _generateSign($params) {

		$fields = array('method', 'ts', 'client');
		sort($fields);

		$request = array();
		foreach ($fields as $k) {
			$request[$k] = $params[$k];
		}

		return substr(md5(http_build_query($request, '', '&') . $this->_getSecret($params['client'])), 8, 16);
	}
}
